﻿#include "ui/textfield.h"
#include "context.h"
#include "draw.h"

using namespace std;
using namespace anip;

// ANIP_UNDONE: selection

ui::TextField::TextField(Container* parent, invec2 pos, invec2 size, const wstring& text):
	WithText(parent, pos, size, text)
{}

void ui::TextField::setText(const wstring& t)
{
	WithText::setText(t);
	if (_cindex > t.size())
		setCursorIndex(t.size());
}

void ui::TextField::processMouseEnter()
{
	postSignal({ ui::Signal::Type::NeedsRedraw });
	Control::processMouseEnter();
}

void ui::TextField::processMouseLeave()
{
	postSignal({ ui::Signal::Type::NeedsRedraw });
	Control::processMouseLeave();
}

void ui::TextField::processMouseMove(invec2 pos)
{
	Context::setCursor(KnownCursor::IBeam);
}

void ui::TextField::processMouseButton(MouseButton btn, ButtonAction action)
{
	if (btn == MouseButton::Left && action == ButtonAction::Down)
	{
		if (_chs == nullptr || _text.empty()) return;

		float borderWid = r::Textfield_BorderWidth;
		vec2 border = { borderWid + roundRadius, borderWid + roundRadius };
		vec2 pos = Context::mousePos() - _absPos - border - scroll();
		int newpos = -1;
		for (unsigned int i = 0; i < _text.length(); i++)
		{
			auto& c = _chs[i];
			if (c.col < 0) continue;
			if (c.y <= pos.y && c.x <= pos.x)
			{
				newpos = i;
				if (pos.x >= c.x + c.data.width * 0.5f)
					newpos++;
			}
			else if (c.y > pos.y)
				break;
		}

		if (newpos != -1) setCursorIndex(newpos);
	}
}

void ui::TextField::processFocus()
{
	postSignal({ ui::Signal::Type::NeedsRedraw });
	Control::processFocus();
}

void ui::TextField::processDefocus()
{
	postSignal({ ui::Signal::Type::NeedsRedraw });
	Control::processDefocus();
}


void ui::TextField::processKey(Key k, ButtonAction action)
{
	if (action == ButtonAction::Down || action == ButtonAction::Repeat)
	{
		int newpos;
		switch (k)
		{
		case Key::Backspace:
			if (_cindex == 0) break;
			_text.erase(_text.begin() + _cindex - 1);
			discardTextTexture();
			setCursorIndex(_cindex - 1);
			break;

		case Key::Delete:
			if (_cindex == _text.length()) break;
			_text.erase(_text.begin() + _cindex);
			discardTextTexture();
			break;

		case Key::Left:
			setCursorIndex(_cindex - 1);
			break;

		case Key::Right:
			setCursorIndex(_cindex + 1);
			break;

		case Key::Up:
			if (_cline == 0 || _chs == nullptr) break;
			newpos = _cindex;
			for (int i = 0; i < _cindex; i++)
			{
				auto& c = _chs[i];
				if (c.col < 0) continue;
				if (c.line == _cline - 1 && c.x <= _cpos.x)
					newpos = i;
				else if (c.line >= _cline - 1 && c.x > _cpos.x)
					break;
			}
			setCursorIndex(newpos);
			break;

		case Key::Down:
			if (_cindex == _text.length() || _chs == nullptr) break;
			newpos = _cindex;
			for (unsigned int i = _cindex; i < _text.length(); i++)
			{
				auto& c = _chs[i];
				if (c.col < 0) continue;
				if (c.line == _cline + 1 && c.x <= _cpos.x)
					newpos = i;
				if (c.line > _cline + 1)
					break;
			}
			setCursorIndex(newpos);
			break;

		case Key::Enter:
		case Key::KpEnter:
			if (multiline)
				inputCharacter(L'\n', action == ButtonAction::Repeat);
			break;

		default:
			// It seems that processUnicodeInput can cover all visible 
			// characters that the user inputs, and processing here will 
			// cause them to be inserted twice
			break;
		}
	}

	WithText::processKey(k, action);
}

void ui::TextField::processUnicodeInput(wchar_t ch)
{
	inputCharacter(ch);
}

void ui::TextField::inputCharacter(wchar_t ch, bool repeat)
{
	if (onInput)
		onInput(*this, ch, repeat);
	if (ch == 0) return;

	postSignal({ ui::Signal::Type::NeedsRedraw });
	deleteSelection();
	_text.insert(_text.begin() + _cindex, ch);
	discardTextTexture();
	setCursorIndex(_cindex + 1);

	if (onTextChanged)
		onTextChanged(*this);
}

void ui::TextField::deleteSelection()
{
	// nothing yet
}

// A cursor at index i means that it comes before the i-th
// character, EXCEPT when i == _text.length() where it comes
// after the (i-1)th character (also see the newline-ending
// special case below). So when the cursor points to the end
// of a line in a multiline TextField, it is actually 
// pointing to the invisible newline character that 
// anip::TextRenderer puts at that position.
void ui::TextField::setCursorIndex(int i)
{
	postSignal({ ui::Signal::Type::NeedsRedraw });

	getTextTexture();

	i = clamp(i, 0, (int)_text.length());
	_cindex = i;

	int width = 0;
	if (i == 0)
	{
		_cpos = { 0, 0 };
		_ccol = 0;
		_cline = 0;
	}
	else if (_chs != nullptr)
	{
		if (i == _text.length())
		{
			auto& c = _chs[i - 1];
			if (c.ch == L'\n')
			{
				// special-case if the last character is \n
				// we want the cursor appear at the line below
				_cpos = { 0, (float)(c.y + _font.size) };
				_ccol = 0;
				_cline = c.line + 1;
			}
			else
			{
				_cpos = { (float)(c.x + c.data.hAdvance), (float)c.y };
				_ccol = c.col + 1;
				_cline = c.line;
				//_cheight = c.data.height;
			}
		}
		else
		{
			auto& c = _chs[i];
			_cpos = { (float)c.x, (float)c.y };
			_ccol = c.col;
			_cline = c.line;
			//_cheight = c.data.height;
			width = c.data.hAdvance;
		}
	}

	if (_cpos.x + width > scroll().x + _size.x)
		scroll = { _cpos.x + width - _size.x, scroll().y };
	else if (_cpos.x < scroll().x)
		scroll = { _cpos.x, scroll().y };
	if (_cpos.y > scroll().y + _size.y)
		scroll = { scroll().x, _cpos.y - _size.y };
	else if (_cpos.y < scroll().y)
		scroll = { scroll().x, _cpos.y };

	//debug(L"setCursorIndex(%d)\n", i);
	//debug(L"_cpos = (%d, %d)\n_ccol = %d\t_cline = %d\nscroll = (%f, %f)\n\n",
	//	(int)_cpos.x, (int)_cpos.y, _ccol, _cline, scroll().x, scroll().y);
}

void ui::TextField::discardTextTexture()
{
	WithText::discardTextTexture();
	if (_layout != nullptr)
	{
		delete _layout;
		_layout = nullptr;
	}
	_chs = nullptr;
}

void ui::TextField::renderText(Texture2D*& textTex, outvec2 size)
{
	if (_layout != nullptr)
	{
		delete _layout;
		_layout = nullptr;
	}
	_chs = nullptr;

	if (_font.isNull()) return;

	if (_text.empty())
	{
		_maxScroll = { 0, 0 };
	}
	else
	{
		int _;
		float border = r::Textfield_BorderWidth + roundRadius;
		TextRenderer::Options opt;
		opt.maxWidth = multiline ? (int)(_size.x - 2 * border) : INT_MAX;
		_layout = TextRenderer::layoutPixelPerfectString(_text, _font, opt);
		_chs = TextRenderer::getLayout(*_layout, _);
		textTex = TextRenderer::renderString(*_layout);
		size = _layout->drawingSize();
		_maxScroll = { 
			max(0.0f, textTex->width() - _size.x), 
			max(0.0f, textTex->height() - _size.y) };
	}
}

void ui::TextField::paint()
{
	float borderWid = r::Textfield_BorderWidth;
	vec2 border = { borderWid, borderWid };
	vec2 start = border + vec2{ roundRadius, roundRadius };

	color borderCol = borderColor;
	if (_focus) borderCol = focusedBorderColor;
	else if (_hover) borderCol = borderColor().mulWithoutAlpha(hoverFactor);

	Draw::roundedRect(_absPos, _size, roundRadius, borderCol);
	Draw::roundedRect(_absPos + border, _size - border * 2.0f, roundRadius - borderWid, backColor);

	Context::addScissor(_absPos + start, _size - start * 2.0f);

	auto textTex = getTextTexture();
	if (textTex != nullptr)
	{
		vec2 tsize = textTex->size();
		Draw::text(_absPos + start - scroll(), tsize, *textTex, textColor);
	}

	if (_focus) Draw::rect(_absPos + start + _cpos - scroll(),
			{ r::CursorWidth, (float)_font.size }, cursorColor);

	Context::noScissor();
	drawScrollers();
}

void ui::TextField::flow(Flow& f)
{
	if (!layoutOptions.noAutoSize && !multiline)
	{
		float border = r::Textfield_BorderWidth + roundRadius;
		_size.y = (float)_font.size + border * 2;
	}

	if (layoutOptions.type != LayoutOptions::Flow)
	{
		Control::flow(f);
		return;
	}

	defaultFlowLayout(f);

	// ANIP_TODO: should flow more carefully and see if is multiline etc.
}